package com.cmii.sjw.department.control.jsc.util;

import com.cmii.sjw.department.control.common.PropertyDescriptorCache;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 对象工具类
 *
 * @author <a href="mailto:tanghaotian@alu.uestc.edu.cn" rel="nofollow">cddysq</a>
 * @version 1.0.0
 * @date 2024/6/13 9:51
 * @since 1.0.0
 **/
@Slf4j
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class ObjectUtils {
    /**
     * 默认排除字段
     */
    public static final Set<String> DEFAULT_EXCLUDE_FIELDS = Set.of("id", "isDeleted", "updateTime", "createTime");

    /**
     * 比较两个集合中的对象是否相等，先基于哈希值、再基于属性值比较，排除默认的字段
     *
     * @param <T>   对象的类型
     * @param list1 第一个集合
     * @param list2 第二个集合
     * @return 如果两个集合中的对象相等，返回 true；否则返回 false
     */
    public static <T> boolean areCollectionsEqual(List<T> list1, List<T> list2) {
        return areCollectionsEqual(list1, list2, DEFAULT_EXCLUDE_FIELDS);
    }

    /**
     * 比较两个集合中的对象是否相等，基于属性值比较，排除指定的字段
     *
     * @param <T>           对象的类型
     * @param list1         第一个集合
     * @param list2         第二个集合
     * @param excludeFields 要排除的字段集合
     * @return 如果两个集合中的对象相等，返回 true；否则返回 false
     */
    public static <T> boolean areCollectionsEqual(List<T> list1, List<T> list2, Set<String> excludeFields) {
        if (list1 == null && list2 == null) {
            return true;
        }
        // 检查两个集合是否有一个为 null 或者大小不相等
        if (CollectionUtils.isEmpty(list1) || CollectionUtils.isEmpty(list2) || list1.size() != list2.size()) {
            return false;
        }

        // 预处理阶段：组装哈希表 k -> hash值，v -> 相同hash值对象列表
        Map<Integer, List<T>> hashTable1 = list1.parallelStream()
                .collect(Collectors.groupingByConcurrent(obj -> calculateHash(obj, excludeFields)));
        Map<Integer, List<T>> hashTable2 = list2.parallelStream()
                .collect(Collectors.groupingByConcurrent(obj -> calculateHash(obj, excludeFields)));
        // 如果两个哈希表的大小不相等，直接返回 false
        if (hashTable1.size() != hashTable2.size()) {
            return false;
        }

        // 并行处理阶段：利用并行流进行并行处理
        AtomicBoolean result = new AtomicBoolean(true);

        hashTable1.entrySet().parallelStream().forEach(entry -> {
            int hash = entry.getKey();
            List<T> subList1 = entry.getValue();
            List<T> subList2 = hashTable2.get(hash);
            // 如果哈希值对应的子列表不存在或大小不相等，直接设置结果为 false
            if (subList2 == null || subList1.size() != subList2.size()) {
                result.set(false);
                return;
            }

            // 比较哈希值相同的对象的属性
            for (int i = 0; i < subList1.size(); i++) {
                if (!arePropertiesEqual(subList1.get(i), subList2.get(i), excludeFields)) {
                    result.set(false);
                    return;
                }
            }
        });

        return result.get();
    }

    /**
     * 比较两个对象的属性值是否相等，排除默认的字段
     *
     * @param <T>  对象的类型
     * @param obj1 第一个对象
     * @param obj2 第二个对象
     * @return 如果两个对象的属性值相等，返回 true；否则返回 false
     */
    public static <T> boolean arePropertiesEqual(T obj1, T obj2) {
        return arePropertiesEqual(obj1, obj2, DEFAULT_EXCLUDE_FIELDS);
    }

    /**
     * 比较两个对象的属性值是否相等，排除指定的字段
     *
     * @param <T>           对象的类型
     * @param obj1          第一个对象
     * @param obj2          第二个对象
     * @param excludeFields 要排除的字段集合
     * @return 如果两个对象的属性值相等，返回 true；否则返回 false
     */
    public static <T> boolean arePropertiesEqual(T obj1, T obj2, Set<String> excludeFields) {
        if (obj1 == null && obj2 == null) {
            return true;
        }
        if (obj1 == null || obj2 == null) {
            return false;
        }
        // 获取对象的属性描述符
        PropertyDescriptor[] descriptors = PropertyDescriptorCache.getPropertyDescriptors(obj1.getClass());

        // 并行比较两个对象的属性值
        return Stream.of(descriptors)
                .parallel()
                // 过滤掉排除的字段
                .filter(descriptor -> !excludeFields.contains(descriptor.getName()))
                .allMatch(descriptor -> {
                    Object value1 = invokeReadMethod(descriptor, obj1);
                    Object value2 = invokeReadMethod(descriptor, obj2);
                    // 双null -> true,一个值为 null，另一个不为 null -> false,有值相等true,有值不相等false
                    return (value1 == null && value2 == null) || (value1 != null && value1.equals(value2));
                });
    }

    /**
     * 基于哈希值比较两个对象的属性值是否相等，排除默认的字段
     *
     * @param <T>  对象的类型
     * @param obj1 第一个对象
     * @param obj2 第二个对象
     * @return 如果两个对象的属性值相等，返回 true；否则返回 false
     */
    public static <T> boolean arePropertiesHashEqual(T obj1, T obj2) {
        return arePropertiesHashEqual(obj1, obj2, DEFAULT_EXCLUDE_FIELDS);
    }

    /**
     * 基于哈希值比较两个对象的属性值是否相等，排除指定的字段
     *
     * @param <T>           对象的类型
     * @param obj1          第一个对象
     * @param obj2          第二个对象
     * @param excludeFields 要排除的字段集合
     * @return 如果两个对象的属性值相等，返回 true；否则返回 false
     */
    public static <T> boolean arePropertiesHashEqual(T obj1, T obj2, Set<String> excludeFields) {
        if (obj1 == null && obj2 == null) {
            return true;
        }
        if (obj1 == null || obj2 == null) {
            return false;
        }
        // 计算两个对象的哈希值并比较
        int hash1 = calculateHash(obj1, excludeFields);
        int hash2 = calculateHash(obj2, excludeFields);
        return hash1 == hash2;
    }

    /**
     * 计算对象的哈希值，排除指定的字段
     *
     * @param obj           要计算哈希值的对象
     * @param excludeFields 要排除的字段集合
     * @return 对象的哈希值
     */
    private static int calculateHash(Object obj, Set<String> excludeFields) {
        PropertyDescriptor[] descriptors = PropertyDescriptorCache.getPropertyDescriptors(obj.getClass());
        String concatenatedValues = Stream.of(descriptors)
                .parallel()
                .filter(descriptor -> !excludeFields.contains(descriptor.getName()))
                .map(descriptor -> {
                    Object value = invokeReadMethod(descriptor, obj);
                    return value == null ? "null" : value.toString();
                })
                .collect(Collectors.joining("|"));
        return concatenatedValues.hashCode();
    }

    /**
     * 调用属性描述符的读取方法,取属性值
     *
     * @param descriptor 属性描述符
     * @param obj        要读取属性值的对象
     * @return 属性值
     */
    private static Object invokeReadMethod(PropertyDescriptor descriptor, Object obj) {
        try {
            return descriptor.getReadMethod().invoke(obj);
        } catch (IllegalAccessException | InvocationTargetException e) {
            log.error("调用属性描述符的读取方法失败", e);
            return null;
        }
    }
}
